﻿Imports System.Windows.Forms
Imports SPRemoteService.Impl

Public Class SharePointTreeView
    Inherits TreeView

    Private _spConnector As SharePointConnector
    Private _selected As New SharePointLocation
    Private _containerTypes As SharePointContainer.TypeEnum _
                            = SharePointContainer.TypeEnum.Site _
                            Or SharePointContainer.TypeEnum.Library _
                            Or SharePointContainer.TypeEnum.Folder
    Private ReadOnly _fieldTypes As New List(Of SharePointField.TypeEnum)

    Public Property ItemLimit() As Integer = 100

    Public Property LibraryBaseTypes() As SharePointLibrary.LibraryBaseType = SharePointLibrary.LibraryBaseType.DocumentLibrary

    Public Property ShowHidden As Boolean

    Public Property ShowReadOnly As Boolean

    Public Property ContainerTypes() As SharePointContainer.TypeEnum
        Get
            Return _containerTypes
        End Get
        Set(ByVal value As SharePointContainer.TypeEnum)

            Dim includesSite = (value And SharePointContainer.TypeEnum.Site) = SharePointContainer.TypeEnum.Site
            If Not includesSite Then
                Throw New Exception()
            End If
            Dim includesLibrary = (value And SharePointContainer.TypeEnum.Library) = SharePointContainer.TypeEnum.Library
            Dim includesFolder = (value And SharePointContainer.TypeEnum.Folder) = SharePointContainer.TypeEnum.Folder
            If includesFolder AndAlso Not includesLibrary Then
                Throw New Exception("Library not found")
            End If

            Const supportedTypes As SharePointContainer.TypeEnum = _
                   SharePointContainer.TypeEnum.Site _
                Or SharePointContainer.TypeEnum.Library _
                Or SharePointContainer.TypeEnum.Folder
            If ((Not supportedTypes) And value) <> 0 Then
                Throw New Exception()
            End If

            _containerTypes = value
        End Set
    End Property

    Public ReadOnly Property FieldTypes() As List(Of SharePointField.TypeEnum)
        Get
            Return _fieldTypes
        End Get
    End Property

    Public Property Selected() As SharePointLocation
        Get
            Return _selected
        End Get
        Set(ByVal value As SharePointLocation)

            If value Is Nothing OrElse value.Equals(Nothing) Then
                Return
            End If

            If value.TargetUrl Is Nothing Then
                Throw New Exception()
            End If


            Dim rootNode = GetChildNode(Nodes, SharePointContainer.TypeEnum.Site, _
                                         Function(c) c.Path = _spConnector.SiteUrl)


            Dim targetNode As SharePointTreeNode = rootNode
            If value.TargetUrl IsNot Nothing Then
                Dim strTargetUrl As String = value.TargetUrl.ToString() + "/"
                Dim relativeTargetUrl = strTargetUrl.Substring(rootNode.Container.Path.ToString().Length)
                Dim targetUrlParts = relativeTargetUrl.ToString().Split("/".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)

                For i As Integer = 0 To targetUrlParts.Length - 1

                    Dim numParts = i + 1
                    Dim partialPath = String.Join("/", _
                                                     (From p In targetUrlParts Take numParts Select p).ToArray())
                    Dim searchUrl As Uri = rootNode.Container.Path.AppendFolder(partialPath)
                    targetNode = GetChildNode(targetNode.Nodes, SharePointContainer.TypeEnum.Site, _
                                                    Function(c) c.Path = searchUrl)
                Next
            End If

            Dim folderParentNode As SharePointTreeNode
            If String.IsNullOrEmpty(value.LibraryId) Then
                If String.IsNullOrEmpty(value.LibraryDisplayName) Then
                    folderParentNode = targetNode
                Else
                    folderParentNode = GetChildNode(targetNode.Nodes, SharePointContainer.TypeEnum.Library, _
                                                     Function(c) String.Compare(c.Name, value.LibraryDisplayName, StringComparison.OrdinalIgnoreCase) = 0)
                End If
            Else
                folderParentNode = GetChildNode(targetNode.Nodes, SharePointContainer.TypeEnum.Library, _
                                                 Function(c) String.Compare(c.Id, value.LibraryId, StringComparison.OrdinalIgnoreCase) = 0)
            End If

            Dim folderNode As SharePointTreeNode = Nothing
            If Not String.IsNullOrEmpty(value.FolderNames) Then
                Dim folderNames As String() = value.FolderNames.Split(New Char() {value.FolderSeparator}, StringSplitOptions.RemoveEmptyEntries)
                For i As Integer = 0 To folderNames.Count() - 1
                    Dim folderName = folderNames(i)
                    folderNode = GetChildNode(folderParentNode.Nodes, SharePointContainer.TypeEnum.Folder, _
                                               Function(c) String.Compare(c.Name, folderName, StringComparison.OrdinalIgnoreCase) = 0)
                    folderParentNode = folderNode
                Next
            End If

            If folderNode IsNot Nothing Then
                SelectedNode = folderNode
            ElseIf folderParentNode IsNot Nothing Then
                SelectedNode = folderParentNode
            End If
        End Set
    End Property

    Public Property TreeConnector() As SharePointConnector
        Get
            Return _spConnector
        End Get
        Set(ByVal value As SharePointConnector)
            _spConnector = value
            Nodes.Clear()
            If _spConnector IsNot Nothing Then
                Dim rootSite As SharePointContainer
                rootSite = _spConnector.GetSite(_spConnector.SiteUrl)
                Dim rootNode = New SharePointTreeNode(rootSite, ItemLimit)
                Nodes.Add(rootNode)
            End If
        End Set
    End Property

    Sub New()
        InitializeComponent()

        IconImageList.Images.Add(My.Resources.folder)
        IconImageList.Images.Add(My.Resources.folder)
        IconImageList.Images.Add(My.Resources.folder)
        IconImageList.Images.Add(My.Resources.folder)
        IconImageList.Images.Add(My.Resources.folder)
        IconImageList.Images.Add(My.Resources.folder)
        IconImageList.Images.Add(My.Resources.folder)
        IconImageList.Images.Add(My.Resources.folder)
        IconImageList.Images.Add(My.Resources.folder)
        IconImageList.Images.Add(My.Resources.folder)
        IconImageList.Images.Add(My.Resources.folder)
    End Sub

    Friend Function GetChildNode(ByVal nodeCollection As TreeNodeCollection, _
                                  ByVal containerType As SharePointContainer.TypeEnum, _
                                  ByVal isMatch As Func(Of SharePointContainer, Boolean)) As SharePointTreeNode
        Dim childNode As SharePointTreeNode

        childNode = (From n As SharePointTreeNode In nodeCollection _
            Where n.Container.ContainerType = containerType AndAlso isMatch(n.Container) _
             Select n).FirstOrDefault()
        If childNode Is Nothing Then
            Throw New Exception("Node not found")
        End If

        CreateChildNodes(childNode)
        Return childNode
    End Function

    Friend Function GetChildNode(ByVal nodeCollection As TreeNodeCollection, _
                                  ByVal fieldTypes As List(Of SharePointField.TypeEnum), _
                                  ByVal isMatch As Func(Of SharePointField, Boolean)) As SharePointFieldTreeNode
        Dim childNode As SharePointFieldTreeNode
        Dim fieldNodeCollection = New List(Of SharePointFieldTreeNode)

        For Each node As TreeNode In nodeCollection
            If TypeOf node Is SharePointFieldTreeNode Then
                fieldNodeCollection.Add(node)
            End If
        Next

        childNode = (From n As SharePointFieldTreeNode In fieldNodeCollection _
            Where fieldTypes.Contains(n.Field.Type) AndAlso isMatch(n.Field) _
             Select n).FirstOrDefault()
        If childNode Is Nothing Then
            Throw New Exception("Node not found")
        End If

        Return childNode
    End Function

    Private Sub CreateChildNodes(ByVal parentNode As SharePointTreeNode)
        If parentNode Is Nothing Then
            Return
        End If

        If parentNode.QueryState <> SharePointTreeNode.QueryStateEnum.Unknown Then
            Return
        End If

        parentNode.Nodes.Clear()

        Select Case parentNode.Container.ContainerType
            Case SharePointContainer.TypeEnum.Site
                CreateChildNodes(parentNode, SharePointContainer.TypeEnum.Site)
                CreateChildNodes(parentNode, SharePointContainer.TypeEnum.Library)
                Exit Select
            Case SharePointContainer.TypeEnum.Library
                CreateChildNodes(parentNode, SharePointContainer.TypeEnum.Folder)
                Exit Select
            Case SharePointContainer.TypeEnum.Folder
                CreateChildNodes(parentNode, SharePointContainer.TypeEnum.Folder)
                Exit Select

        End Select

        parentNode.Expand()

    End Sub

    Private Sub CreateChildNodes(ByVal parentNode As SharePointTreeNode, ByVal childType As SharePointContainer.TypeEnum)
        If parentNode.MaxChildrenDisplayed OrElse (childType <> (ContainerTypes And childType)) Then
            Return
        End If

        Dim childContainers As List(Of SharePointContainer) = Nothing
        Select Case childType
            Case SharePointContainer.TypeEnum.Site
                childContainers = TreeConnector.GetSiteList(parentNode.SiteUrl)

            Case SharePointContainer.TypeEnum.Library
                childContainers = (From l In TreeConnector.GetLibraryList(parentNode.SiteUrl) _
                  Where ((LibraryBaseTypes = SharePointLibrary.LibraryBaseType.Any) OrElse _
                         (CType(l, SharePointLibrary).BaseType And LibraryBaseTypes) = CType(l, SharePointLibrary).BaseType) _
                        AndAlso (l.Hidden = ShowHidden OrElse Not l.Hidden) _
                  Select CType(l, SharePointContainer)).ToList()

            Case SharePointContainer.TypeEnum.Folder
                childContainers = TreeConnector.GetFolderList(parentNode.SiteUrl, parentNode.Library.Name, parentNode.ContainerFolderPath, ItemLimit.ToString)
        End Select

        parentNode.QueryState = SharePointTreeNode.QueryStateEnum.QueryCompleted

        If childContainers IsNot Nothing Then
            For Each childContainer As SharePointContainer In childContainers
                If parentNode.MaxChildrenDisplayed Then
                    Exit For
                End If

                Dim childNode As SharePointTreeNode = New SharePointTreeNode(childContainer, ItemLimit)
                If parentNode.ID <> childNode.ID Then
                    parentNode.Nodes.Add(childNode)
                End If
            Next
        End If
    End Sub

    Private Sub SharePointTreeAfterSelect(ByVal sender As System.Object, ByVal e As Windows.Forms.TreeViewEventArgs) Handles MyBase.AfterSelect
        Dim node As SharePointTreeNode
        Dim selection As New SharePointLocation
        If TypeOf e.Node Is SharePointTreeNode Then
            node = CType(e.Node, SharePointTreeNode)
            CreateChildNodes(node)
        Else
            Dim fieldNode As SharePointFieldTreeNode

            fieldNode = CType(e.Node, SharePointFieldTreeNode)

            selection.ColumnId = fieldNode.Field.Id()
            selection.ColumnDisplayName = fieldNode.Field.DisplayName()

            node = CType(fieldNode.Parent, SharePointTreeNode)
        End If

        selection.FolderSeparator = Selected.FolderSeparator

        While node IsNot Nothing
            Select Case node.Container.ContainerType
                Case SharePointContainer.TypeEnum.Site
                    If selection.TargetUrl Is Nothing Then
                        selection.TargetUrl = node.Container.Path
                    End If
                Case SharePointContainer.TypeEnum.Library
                    selection.LibraryDisplayName = node.Container.Name
                    selection.LibraryId = node.Container.Id

                Case SharePointContainer.TypeEnum.Folder
                    If selection.FolderNames.Length > 0 Then
                        selection.FolderNames = selection.FolderSeparator & selection.FolderNames
                    End If
                    selection.FolderNames = node.Container.Name & selection.FolderNames
            End Select

            node = node.Parent
        End While

        _selected = selection

    End Sub

End Class
